package com.qianxun.plugins.log;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;

/**
 * 参数解析器
 * @author: huwei
 * @date: 2019/10/16 10:26
 * @version: 1.0.0
 */
public class ParameterResolver {

    /**
     * 解析方法参数名和参数值的map映射列
     * @param method 方法
     * @param args 参数值列表，该列表中的参数值个数需要与参数个数一一对应，否则将抛出异常
     * @return 参数名和参数值的map映射列表，映射失败，返回空的map ，反之
     */
    public static Map<String ,Object> parseNameValueMap(Method method ,Object[] args){
        return parseNameValueMap(method, args ,null);
    }

    /**
     /**
     * 解析方法参数名和参数值的map映射列
     * @param method 方法
     * @param args 参数值列表，该列表中的参数值个数需要与参数个数一一对应，否则将抛出异常
     * @param paramInfo 参数名注解类型，优先使用注解名获取的方式设置参数名，若无注解则使用jdk1.8的参数名反射获取
     * @return 参数名和参数值的map映射列表，映射失败，返回空的map ，反之
     */
    public static Map<String ,Object> parseNameValueMap(Method method ,Object[] args ,Class<? extends LogParam> paramInfo){
        checkParameterMapping(method ,args);
        return paramInfo != null ? annotationParse(method,args,paramInfo) : reflectParse(method, args);
    }

    /**
     * 利用@Param及子类注解的方式获取参数名称，并与参数值形成参数Map映射；
     * 注意：该方式可以确保使用@param及子类注解的参数能够正确的获取到参数名，并与参数值形成映射关系，而未使用注解的参数将不能得到保证
     * @param method 方法
     * @param args 参数列表
     * @param paramInfo 注解类型
     * @return 返回参数名与参数值的映射关系，无任何映射时返回空的map列表，反之返回map映射列表
     */
    private static Map<String ,Object> annotationParse(Method method ,Object[] args ,Class<? extends LogParam> paramInfo){
        Map<String ,Object> res = new HashMap<>(args.length);

        Parameter[] parameters = method.getParameters();
        for(int i = 0 ,len = parameters.length ;i < len ;++i){
            Parameter parameter = parameters[i];
            LogParam parameterDeclaredAnnotation = parameter.getDeclaredAnnotation(paramInfo);
            String parameterName = null;
            //该参数没有设置@Param及子类注解，尝试使用jdk1.8自带的反射机制获取
            if(parameterDeclaredAnnotation == null){
                if(parameter.isNamePresent()){
                    parameterName = parameter.getName();
                }else{
                    continue;
                }
            }else{
                parameterName = parameterDeclaredAnnotation.value();
            }
            res.put(parameterName ,args[i]);
        }
        return res;
    }

    /**
     * 利用jdk1.8自带反射机制获取方法的参数名称，并与参数值形成map映射
     * 注意：该方式需要编译源码时开启-parameter参数，即 javac -parameter ，若为开启则可能获取不到参数名称
     * @param method 方法
     * @param args 参数列表
     * @return 当可获取参数名时，返回参数与参数值的map映射，反之返回空的map列表
     */
    private static Map<String ,Object> reflectParse(Method method ,Object[] args){
        Map<String ,Object> res = new HashMap<>(args.length);

        Parameter[] parameters = method.getParameters();
        for (int i = 0 ,len = parameters.length ;i < len ;++i) {
            Parameter parameter = parameters[i];
            if (parameter.isNamePresent()) {
                break;
            }
            res.put(parameter.getName() ,args[i]);
        }
        return res;
    }

    private static void checkParameterMapping(Method method ,Object[] args){
        Parameter[] parameters = method.getParameters();
        if(parameters.length != args.length){
            throw new RuntimeException("参数与参数值列表不匹配，参数个数:".concat(String.valueOf(parameters.length)).concat(" ,参数值个数:").concat(String.valueOf(args.length)));
        }
    }

}
